package bll

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/RoaringBitmap/roaring/v2/roaring64"
	"github.com/lib/pq"
	"pay/model"
	"pay/model/entity"
	"pay/model/mapping"
	"pay/store"
	"pay/store/postgres"
	"pay/utils"
	"pay/utils/bitmap"
	"time"
)

type feePlanGroup struct {
	iFeePlanGroup       store.IFeePlanGroup
	iFeePlanGroupObject store.IFeePlanGroupObject
}

var FeePlanGroup = &feePlanGroup{
	iFeePlanGroup:       postgres.FeePlanGroup,
	iFeePlanGroupObject: postgres.FeePlanGroupObject,
}

func init() {
	Register(FeePlanGroup)
}

func (a *feePlanGroup) init() func() {
	return func() {}
}

func (a *feePlanGroup) check(ctx context.Context, in *model.CheckRequest) (json.RawMessage, *entity.FeePlanGroup, error) {

	var (
		err             error
		respPlanGroup   *entity.FeePlanGroup
		respFeePlanList *model.FeePlanListResponse
	)

	if in.Id != nil {
		if respPlanGroup, err = a.iFeePlanGroup.Find(ctx, &model.FeePlanGroupInfoRequest{
			Id: *in.Id,
		}); err != nil {
			return nil, respPlanGroup, err
		}
	}

	ctx, _ = context.WithTimeout(ctx, time.Minute)
	respFeePlanList, err = FeePlan.List(ctx, &model.FeePlanListRequest{
		Index: 1,
		Size:  len(in.FeePlanIds),
		Ids:   in.FeePlanIds,
	}, model.QueryOptions{
		OnlyList: true,
	})
	if err != nil {
		return nil, nil, err
	}

	/* 选定为默认，就是绑定所有车辆 */
	if in.Def == 1 {
		ba := roaring64.New()
		for _, v := range in.EBikeIds {
			if ba.Contains(uint64(v)) {
				return nil, respPlanGroup, fmt.Errorf("不可以包含重复的车辆")
			}
			ba.Add(uint64(v))
		}
	}

	var (
		allowPlanTypes = map[model.FeePlanType]*struct {
			selectedCount int
			allowCount    int
		}{
			model.FeePlanTypeYearlyPlan: {
				allowCount: 1,
			},
			model.FeePlanTypeYearlyRenewalPlan: {
				allowCount: 1,
			},
		}
		planIds   = map[int64]struct{}{}
		planPacks model.PlanPacks
	)

	for _, v := range respFeePlanList.List {

		fpType := model.FeePlanType(v.Type)
		if _, ok := planIds[v.Id]; ok {
			return nil, respPlanGroup, fmt.Errorf("所选套餐计划重复了")
		}

		if vv, ok := allowPlanTypes[fpType]; ok {
			if vv.selectedCount > vv.allowCount {
				return nil, respPlanGroup, fmt.Errorf("%v套餐类型只可以选择一个", fpType.String())
			} else {
				allowPlanTypes[fpType] = &struct {
					selectedCount int
					allowCount    int
				}{
					selectedCount: vv.selectedCount + 1,
					allowCount:    vv.allowCount,
				}
			}
		}
		planPacks = append(planPacks, model.NewPlanPacks(v.Packs, &model.PlanPackOption{
			PlanId: utils.GetValuePointer(v.Id),
			Type:   utils.GetValuePointer(model.FeePlanType(v.Type)),
		})...)
	}

	return planPacks.Bytes(), respPlanGroup, nil
}

// Create
func (a *feePlanGroup) Create(ctx context.Context, in *model.FeePlanGroupCreateRequest) error {
	var (
		groupId             int64
		err                 error
		detailJson          json.RawMessage
		groupObjectRequests []*model.FeePlanGroupObjectCreateRequest
	)

	if detailJson, _, err = a.check(ctx, &model.CheckRequest{EBikeIds: in.EBikeIds, FeePlanIds: in.FeePlanIds, Def: in.Def}); err != nil {
		return err
	}

	if in.Def == 1 {
		if c, _, err := a.iFeePlanGroup.List(ctx, &model.FeePlanGroupListRequest{
			Def: utils.GetValuePointer(1),
		}, model.QueryOptions{OnlyCount: true}); err != nil {
			return err
		} else {
			if c > 0 {
				return fmt.Errorf("默认的套餐组已经存在")
			}
		}
	}

	c := buildFeePlanGroup(in)
	c.FeePlanDetail = detailJson

	return a.iFeePlanGroup.ExecTransaction(ctx, func(ctx context.Context) error {

		groupId, err = a.iFeePlanGroup.Create(ctx, c)
		if err != nil {
			return err
		}

		/* 如果配置的是全部车辆，那么不需要创建 fee_plan_group_object 记录 */
		if in.Def == 1 {
			return nil
		}

		for _, eBikeId := range in.EBikeIds {
			groupObjectRequests = append(groupObjectRequests, &model.FeePlanGroupObjectCreateRequest{
				GroupId:  groupId,
				Detail:   detailJson,
				ObjectId: eBikeId,
			})
		}

		return FeePlanGroupObject.CreateInBatch(ctx, groupObjectRequests, 500)
	})
}

// Update
func (a *feePlanGroup) Update(ctx context.Context, in *model.FeePlanGroupUpdateRequest) error {

	var (
		err                    error
		detailJson             json.RawMessage
		respPlanGroup          *entity.FeePlanGroup
		dict                   = make(map[string]interface{})
		dictFeePlanGroupObject = make(map[string]interface{})
		groupObjectRequests    []*model.FeePlanGroupObjectCreateRequest
	)

	if detailJson, respPlanGroup, err = a.check(ctx, &model.CheckRequest{Id: in.Id, FeePlanIds: in.FeePlanIds, EBikeIds: in.EBikeIds}); err != nil {
		return err
	}

	if in.Id != nil {
		dict["id"] = in.Id
	}

	if in.Name != nil {
		dict["name"] = in.Name
	}

	if in.Remark != nil {
		dict["remark"] = in.Remark
	}

	if in.FeePlanIds != nil {
		if len(in.FeePlanIds) != len(respPlanGroup.FeePlanIds) {
			dictFeePlanGroupObject["detail"] = detailJson
			dict["fee_plan_ids"] = in.FeePlanIds
			dict["fee_plan_detail"] = detailJson
		} else {
			var (
				newFeePlanIds []int64
				oldFeePlanIds []int64
			)
			for _, v := range in.FeePlanIds {
				newFeePlanIds = append(newFeePlanIds, v)
			}
			for _, v := range respPlanGroup.FeePlanIds {
				oldFeePlanIds = append(oldFeePlanIds, v)
			}
			b1, _ := bitmap.ToString(newFeePlanIds...)
			b2, _ := bitmap.ToString(oldFeePlanIds...)
			if b1 != b2 {
				dictFeePlanGroupObject["detail"] = detailJson
				dict["fee_plan_ids"] = in.FeePlanIds
				dict["fee_plan_detail"] = detailJson
			}
		}
	}

	if len(in.EBikeIds) > 0 {
		b, _ := bitmap.ToString(in.EBikeIds...)
		if respPlanGroup.BitmapEbikeIds != b {
			for _, eBikeId := range in.EBikeIds {
				groupObjectRequests = append(groupObjectRequests, &model.FeePlanGroupObjectCreateRequest{
					GroupId:  *in.Id,
					Detail:   detailJson,
					ObjectId: eBikeId,
				})
			}
			dict["bitmap_ebike_ids"] = b
		}
	}

	if in.Def != nil {
		/* 不可以修改默认套餐组 */
		if respPlanGroup.Def != *in.Def {
			return fmt.Errorf("不可以修改默认套餐组")
		}
	}

	if in.Enable != nil {
		dict["enable"] = in.Enable
	}

	if in.CreatedAt != nil {
		dict["created_at"] = in.CreatedAt
	}

	if in.UpdatedAt != nil {
		dict["updated_at"] = in.UpdatedAt
	}

	// do other update here
	updateAt := time.Now().Unix()
	in.UpdatedAt = &updateAt

	err = a.iFeePlanGroup.ExecTransaction(ctx, func(ctx context.Context) error {

		if len(groupObjectRequests) > 0 {
			if err = FeePlanGroupObject.DeleteByGroupId(ctx, &model.FeePlanGroupObjectDeleteByGroupIDRequest{
				GroupID: *in.Id,
			}); err != nil {
				return err
			}

			if err = FeePlanGroupObject.CreateInBatch(ctx, groupObjectRequests, 500); err != nil {
				return err
			}
		}

		if len(dictFeePlanGroupObject) > 0 {
			dictFeePlanGroupObject["updated_at"] = in.UpdatedAt
			if err = a.iFeePlanGroupObject.UpdateByGroupId(ctx, *in.Id, dictFeePlanGroupObject); err != nil {
				return err
			}
		}

		return a.iFeePlanGroup.Update(ctx, *in.Id, dict)
	})

	return err
}

// Delete
func (a *feePlanGroup) Delete(ctx context.Context, in *model.FeePlanGroupDeleteRequest) error {

	return a.iFeePlanGroup.ExecTransaction(ctx, func(ctx context.Context) error {

		if err := a.iFeePlanGroupObject.DeleteByGroupId(ctx, in.Id); err != nil {
			return err
		}
		return a.iFeePlanGroup.Delete(ctx, in.Id)
	})

}

// List
func (a *feePlanGroup) List(ctx context.Context, in *model.FeePlanGroupListRequest, opts ...model.QueryOptions) (*model.FeePlanGroupListResponse, error) {
	var (
		err   error
		total int
		list  []*entity.FeePlanGroup
		out   = &model.FeePlanGroupListResponse{}
	)

	if total, list, err = a.iFeePlanGroup.List(ctx, in, opts...); err != nil {
		return nil, err
	}

	out.Total = total
	out.List = mapping.FeePlanGroupsEntityToDto(list)

	return out, nil
}

// Find
func (a *feePlanGroup) Find(ctx context.Context, in *model.FeePlanGroupInfoRequest) (*model.FeePlanGroupInfo, error) {
	var (
		err  error
		data *entity.FeePlanGroup
		out  = &model.FeePlanGroupInfo{}
	)

	if data, err = a.iFeePlanGroup.Find(ctx, in); err != nil {
		return nil, err
	}

	out = mapping.FeePlanGroupEntityToDto(data)
	return out, nil
}

// buildFeePlanGroup build entity
func buildFeePlanGroup(in *model.FeePlanGroupCreateRequest) *entity.FeePlanGroup {

	ety := &entity.FeePlanGroup{}

	ety.Id = in.Id

	ety.Name = in.Name

	ety.Remark = in.Remark

	ety.BitmapEbikeIds, _ = bitmap.ToString(in.EBikeIds...)

	if len(in.FeePlanIds) != 0 {
		ety.FeePlanIds = in.FeePlanIds
	} else {
		ety.FeePlanIds = pq.Int64Array{}
	}

	ety.Def = in.Def
	ety.Enable = in.Enable

	ety.CreatedAt = time.Now().Unix()

	ety.UpdatedAt = in.UpdatedAt

	return ety
}
